//--------------------------------------------------------------------------------------
// File: ShadowMap.cpp
//--------------------------------------------------------------------------------------
#include "DXUT.h"
#include "DXUTcamera.h"
#include <vector>

using namespace std;

const PTSTR EFFECT_FILE_NAME = TEXT("ShadowMap.fx");
const PTSTR SCENE_MODEL_FILE_NAME = TEXT("BasicColumnScene.x");
const PTSTR CAR_MODEL_FILE_NAME = TEXT("car.x");
const PTSTR WHITE_TEX_FILE_NAME = TEXT("whitetex.dds");
const UINT SMAP_SIZE = 2048;

struct Mtrl
{
	D3DXCOLOR ambient;
	D3DXCOLOR diffuse;
	D3DXCOLOR spec;
	float specPower;
};

struct SpotLight
{
	D3DXCOLOR ambient;
	D3DXCOLOR diffuse;
	D3DXCOLOR spec;
	D3DXVECTOR3 posW;
	D3DXVECTOR3 dirW;
	float spotPower;
};

CFirstPersonCamera g_camera;
ID3DXMesh		*g_sceneMesh;
DWORD			g_sceneNumMtrl;
vector<Mtrl>	g_sceneMtrls;
vector<IDirect3DTexture9*>	g_sceneTextures;
ID3DXMesh		*g_carMesh;
DWORD			g_carNumMtrl;
vector<Mtrl>	g_carMtrls;
IDirect3DTexture9 *g_whiteTexture;
ID3DXEffect		*g_effect;
D3DXHANDLE		g_hBuildShadowMapTech;
D3DXHANDLE		g_hLightShadowTech;
D3DXHANDLE		g_hWorld;
D3DXHANDLE		g_hWorldInvTran;
D3DXHANDLE		g_hWVP;
D3DXHANDLE		g_hMtrl;
D3DXHANDLE		g_hSpotLight;
D3DXHANDLE		g_hEyePosW;
D3DXHANDLE		g_hTex;
D3DXHANDLE		g_hLightWVP;
D3DXHANDLE		g_hShadowMap;

D3DXMATRIX		g_sceneWorld;
D3DXMATRIX		g_sceneWorldInvTran;
D3DXMATRIX		g_carWorld;
D3DXMATRIX		g_carWorldInvTran;
D3DXMATRIX		g_lightVP;
SpotLight		g_spotLight;

ID3DXRenderToSurface*	g_shadowRTS;
IDirect3DTexture9*		g_shadowTexture;
IDirect3DSurface9*		g_shadowTopSurface;

// Methods
void CreateEffect(IDirect3DDevice9 *pd3dDevice);
void CreateScene(IDirect3DDevice9 *pd3dDevice);
void CreateCar(IDirect3DDevice9 *pd3dDevice);
void CreateShadowRTS(IDirect3DDevice9 *pd3dDevice);
void UpdateLight(float fElapsedTime);
void DrawShadowMap(IDirect3DDevice9 *pd3dDevice);
void DrawScene(IDirect3DDevice9 *pd3dDevice);

//--------------------------------------------------------------------------------------
// Rejects any D3D9 devices that aren't acceptable to the app by returning false
//--------------------------------------------------------------------------------------
bool CALLBACK IsD3D9DeviceAcceptable( D3DCAPS9* pCaps, D3DFORMAT AdapterFormat, D3DFORMAT BackBufferFormat,
	bool bWindowed, void* pUserContext )
{
	// Typically want to skip back buffer formats that don't support alpha blending
	IDirect3D9* pD3D = DXUTGetD3D9Object();
	if( FAILED( pD3D->CheckDeviceFormat( pCaps->AdapterOrdinal, pCaps->DeviceType,
		AdapterFormat, D3DUSAGE_QUERY_POSTPIXELSHADER_BLENDING,
		D3DRTYPE_TEXTURE, BackBufferFormat ) ) )
		return false;

	return true;
}


//--------------------------------------------------------------------------------------
// Before a device is created, modify the device settings as needed
//--------------------------------------------------------------------------------------
bool CALLBACK ModifyDeviceSettings( DXUTDeviceSettings* pDeviceSettings, void* pUserContext )
{
	//pDeviceSettings->d3d9.pp.MultiSampleQuality = D3DMULTISAMPLE_2_SAMPLES;
	return true;
}


//--------------------------------------------------------------------------------------
// Create any D3D9 resources that will live through a device reset (D3DPOOL_MANAGED)
// and aren't tied to the back buffer size
//--------------------------------------------------------------------------------------
HRESULT CALLBACK OnD3D9CreateDevice( IDirect3DDevice9* pd3dDevice, const D3DSURFACE_DESC* pBackBufferSurfaceDesc,
	void* pUserContext )
{
	HRESULT hr;
	V(D3DXCreateTextureFromFile(pd3dDevice, WHITE_TEX_FILE_NAME, &g_whiteTexture));

	CreateScene(pd3dDevice);
	CreateCar(pd3dDevice);
	CreateEffect(pd3dDevice);

	g_camera.SetViewParams(&D3DXVECTOR3(0.0f, 50.0f, -50.0f), &D3DXVECTOR3(0.0f, 0.0f, 0.0f));
	return S_OK;
}


//--------------------------------------------------------------------------------------
// Create any D3D9 resources that won't live through a device reset (D3DPOOL_DEFAULT) 
// or that are tied to the back buffer size 
//--------------------------------------------------------------------------------------
HRESULT CALLBACK OnD3D9ResetDevice( IDirect3DDevice9* pd3dDevice, const D3DSURFACE_DESC* pBackBufferSurfaceDesc,
	void* pUserContext )
{
	CreateShadowRTS(pd3dDevice);

	g_camera.SetProjParams(D3DX_PI / 4, pBackBufferSurfaceDesc->Width / (float)pBackBufferSurfaceDesc->Height,
		1.0f, 1000.0f);
	return S_OK;
}


//--------------------------------------------------------------------------------------
// Handle updates to the scene.  This is called regardless of which D3D API is used
//--------------------------------------------------------------------------------------
void CALLBACK OnFrameMove( double fTime, float fElapsedTime, void* pUserContext )
{
	UpdateLight(fElapsedTime);
	g_camera.FrameMove(fElapsedTime);
}


//--------------------------------------------------------------------------------------
// Render the scene using the D3D9 device
//--------------------------------------------------------------------------------------
void CALLBACK OnD3D9FrameRender( IDirect3DDevice9* pd3dDevice, double fTime, float fElapsedTime, void* pUserContext )
{
	HRESULT hr;

	DrawShadowMap(pd3dDevice);

	// Clear the render target and the zbuffer 
	V( pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_ARGB( 0, 45, 50, 170 ), 1.0f, 0 ) );

	// Render the scene
	if( SUCCEEDED( pd3dDevice->BeginScene() ) )
	{
		DrawScene(pd3dDevice);

		V( pd3dDevice->EndScene() );
	}
}


//--------------------------------------------------------------------------------------
// Handle messages to the application 
//--------------------------------------------------------------------------------------
LRESULT CALLBACK MsgProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam,
	bool* pbNoFurtherProcessing, void* pUserContext )
{
	g_camera.HandleMessages(hWnd, uMsg, wParam, lParam);

	return 0;
}


//--------------------------------------------------------------------------------------
// Release D3D9 resources created in the OnD3D9ResetDevice callback 
//--------------------------------------------------------------------------------------
void CALLBACK OnD3D9LostDevice( void* pUserContext )
{
	g_shadowRTS->Release();
	g_shadowTexture->Release();
	g_shadowTopSurface->Release();
}


//--------------------------------------------------------------------------------------
// Release D3D9 resources created in the OnD3D9CreateDevice callback 
//--------------------------------------------------------------------------------------
void CALLBACK OnD3D9DestroyDevice( void* pUserContext )
{
	g_sceneMesh->Release();
	for(auto iter = g_sceneTextures.begin(); iter != g_sceneTextures.end(); ++iter)
	{
		(*iter)->Release();
	}
	g_sceneTextures.clear();
	g_effect->Release();
	g_carMesh->Release();
	g_whiteTexture->Release();
}


//--------------------------------------------------------------------------------------
// Initialize everything and go into a render loop
//--------------------------------------------------------------------------------------
INT WINAPI wWinMain( HINSTANCE, HINSTANCE, LPWSTR, int )
{
	// Enable run-time memory check for debug builds.
#if defined(DEBUG) | defined(_DEBUG)
	_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
#endif

	// Set the callback functions
	DXUTSetCallbackD3D9DeviceAcceptable( IsD3D9DeviceAcceptable );
	DXUTSetCallbackD3D9DeviceCreated( OnD3D9CreateDevice );
	DXUTSetCallbackD3D9DeviceReset( OnD3D9ResetDevice );
	DXUTSetCallbackD3D9FrameRender( OnD3D9FrameRender );
	DXUTSetCallbackD3D9DeviceLost( OnD3D9LostDevice );
	DXUTSetCallbackD3D9DeviceDestroyed( OnD3D9DestroyDevice );
	DXUTSetCallbackDeviceChanging( ModifyDeviceSettings );
	DXUTSetCallbackMsgProc( MsgProc );
	DXUTSetCallbackFrameMove( OnFrameMove );

	// TODO: Perform any application-level initialization here

	// Initialize DXUT and create the desired Win32 window and Direct3D device for the application
	DXUTInit( true, true ); // Parse the command line and show msgboxes
	DXUTSetHotkeyHandling( true, true, true );  // handle the default hotkeys
	DXUTSetCursorSettings( true, true ); // Show the cursor and clip it when in full screen
	DXUTCreateWindow( L"EmptyProject" );
	DXUTCreateDevice( true, 640, 480 );

	// Start the render loop
	DXUTMainLoop();

	// TODO: Perform any application-level cleanup here

	return DXUTGetExitCode();
}


void CreateEffect(IDirect3DDevice9 *pd3dDevice)
{
	HRESULT hr;
	ID3DXBuffer *pCompilationErrors = NULL;
	hr = D3DXCreateEffectFromFile(pd3dDevice, EFFECT_FILE_NAME, NULL, NULL, D3DXSHADER_DEBUG, NULL,
		&g_effect, &pCompilationErrors);
	if(pCompilationErrors)
	{
		MessageBoxA(0, static_cast<char*>(pCompilationErrors->GetBufferPointer()), NULL, 0);
	}
	V(hr);

	g_hBuildShadowMapTech	= g_effect->GetTechniqueByName("BuildShadowMapTech");
	g_hLightShadowTech		= g_effect->GetTechniqueByName("LightShadowTech");
	g_hWorld		= g_effect->GetParameterByName(0, "gWorld");
	g_hWorldInvTran	= g_effect->GetParameterByName(0, "gWorldInvTran");
	g_hWVP			= g_effect->GetParameterByName(0, "gWVP");
	g_hMtrl			= g_effect->GetParameterByName(0, "gMtrl");
	g_hSpotLight	= g_effect->GetParameterByName(0, "gSpotLight");
	g_hEyePosW		= g_effect->GetParameterByName(0, "gEyePosW");
	g_hTex			= g_effect->GetParameterByName(0, "gTex");
	g_hLightWVP		= g_effect->GetParameterByName(0, "gLightWVP");
	g_hShadowMap	= g_effect->GetParameterByName(0, "gShadowMap");
}

void CreateScene(IDirect3DDevice9 *pd3dDevice)
{
	HRESULT hr;
	ID3DXBuffer *d3dMtrlBuffer;
	V(D3DXLoadMeshFromX(SCENE_MODEL_FILE_NAME, D3DXMESH_MANAGED, pd3dDevice, NULL, &d3dMtrlBuffer, NULL,
		&g_sceneNumMtrl, &g_sceneMesh));
	D3DXMATERIAL *d3dMtrl = (D3DXMATERIAL*)d3dMtrlBuffer->GetBufferPointer();
	for(DWORD i = 0; i < g_sceneNumMtrl; i++)
	{
		if(d3dMtrl[i].pTextureFilename)
		{
			Mtrl mtrl;
			//mtrl.ambient = d3dMtrl[i].MatD3D.Ambient;
			mtrl.ambient = D3DXCOLOR(0.5f, 0.5f, 0.5f, 1.0f);
			mtrl.diffuse = d3dMtrl[i].MatD3D.Diffuse;
			mtrl.spec = d3dMtrl[i].MatD3D.Specular;
			mtrl.specPower = d3dMtrl[i].MatD3D.Power;
			g_sceneMtrls.push_back(mtrl);

			IDirect3DTexture9* texture;
			V(D3DXCreateTextureFromFileA(pd3dDevice, d3dMtrl[i].pTextureFilename, &texture));
			g_sceneTextures.push_back(texture);
		}
	}

	D3DXMatrixIdentity(&g_sceneWorld);
	D3DXMatrixIdentity(&g_sceneWorldInvTran);
}

void CreateCar(IDirect3DDevice9 *pd3dDevice)
{
	HRESULT hr;
	ID3DXBuffer *d3dMtrlBuffer;
	V(D3DXLoadMeshFromX(CAR_MODEL_FILE_NAME, D3DXMESH_MANAGED, pd3dDevice, NULL, &d3dMtrlBuffer, NULL,
		&g_carNumMtrl, &g_carMesh));
	D3DXMATERIAL *d3dMtrl = (D3DXMATERIAL*)d3dMtrlBuffer->GetBufferPointer();
	for(DWORD i = 0; i < g_carNumMtrl; i++)
	{
		Mtrl mtrl;
		//mtrl.ambient = d3dMtrl[i].MatD3D.Ambient;
		mtrl.ambient = D3DXCOLOR(0.5f, 0.5f, 0.5f, 1.0f);
		mtrl.diffuse = d3dMtrl[i].MatD3D.Diffuse;
		mtrl.spec = d3dMtrl[i].MatD3D.Specular;
		mtrl.specPower = d3dMtrl[i].MatD3D.Power;
		g_carMtrls.push_back(mtrl);
	}


	D3DXMATRIX S, T;
	D3DXMatrixScaling(&S, 1.5f, 1.5f, 1.5f);
	D3DXMatrixTranslation(&T, 7.0f, 3.5f, -3.0f);
	g_carWorld = S*T;
	g_carWorldInvTran = g_carWorld;
}

void CreateShadowRTS(IDirect3DDevice9 *pd3dDevice)
{
	HRESULT hr;
	V(D3DXCreateRenderToSurface(pd3dDevice, SMAP_SIZE, SMAP_SIZE, D3DFMT_R32F, TRUE, D3DFMT_D24X8, &g_shadowRTS));

	V(D3DXCreateTexture(pd3dDevice, SMAP_SIZE, SMAP_SIZE, 1,
		D3DUSAGE_RENDERTARGET , D3DFMT_R32F, D3DPOOL_DEFAULT, &g_shadowTexture));

	V(g_shadowTexture->GetSurfaceLevel(0, &g_shadowTopSurface));
}

void UpdateLight(float fElapsedTime)
{
	D3DXMATRIX lightView, lightLens;
	D3DXVECTOR3 lightPosW(125.0f, 50.0f, 0.0f);
	D3DXVECTOR3 lightTargetW(0.0f, 0.0f, 0.0f);
	D3DXVECTOR3 lightUpW(0.0f, 1.0f, 0.0f);

	static float t = 0.0f;
	t += fElapsedTime / 10.0f;
	if( t >= 2.0f*D3DX_PI )
		t = 0.0f;
	D3DXMATRIX Ry;
	D3DXMatrixRotationY(&Ry, t);
	D3DXVec3TransformCoord(&lightPosW, &lightPosW, &Ry);

	D3DXMatrixLookAtLH(&lightView, &lightPosW, &lightTargetW, &lightUpW);

	float lightFOV = D3DX_PI*0.25f;
	D3DXMatrixPerspectiveFovLH(&lightLens, lightFOV, 1.0f, 1.0f, 200.0f);

	g_lightVP = lightView * lightLens;

	D3DXVECTOR3 lightDirW = lightTargetW - lightPosW;
	D3DXVec3Normalize(&lightDirW, &lightDirW);
	g_spotLight.posW      = lightPosW;
	g_spotLight.dirW      = lightDirW;
	g_spotLight.ambient   = D3DXCOLOR(0.5f, 0.5f, 0.5f, 1.0f);
	g_spotLight.diffuse   = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);
	g_spotLight.spec      = D3DXCOLOR(0.5f, 0.5f, 0.5f, 1.0f);
	g_spotLight.spotPower = 32.0f;
}

void DrawShadowMap(IDirect3DDevice9 *pd3dDevice)
{
	HRESULT hr;
	V(g_shadowRTS->BeginScene(g_shadowTopSurface, NULL));
	V(pd3dDevice->Clear(0, 0, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_RGBA(0, 0, 0, 0), 1.0f, 0));

	V(g_effect->SetTechnique(g_hBuildShadowMapTech));
	V(g_effect->Begin(NULL, 0));
	V(g_effect->BeginPass(0));

	//draw scene
	V(g_effect->SetMatrix(g_hLightWVP, &(g_sceneWorld * g_lightVP)));
	V(g_effect->CommitChanges());
	for(DWORD i = 0; i < g_sceneNumMtrl; i++)
	{
		g_sceneMesh->DrawSubset(i);
	}

	//draw car
	V(g_effect->SetMatrix(g_hLightWVP, &(g_carWorld * g_lightVP)));
	V(g_effect->CommitChanges());
	for(DWORD i = 0; i < g_carNumMtrl; i++)
	{
		g_carMesh->DrawSubset(i);
	}

	V(g_effect->EndPass());
	V(g_effect->End());
	V(g_shadowRTS->EndScene(D3DX_FILTER_NONE));
}

void DrawScene(IDirect3DDevice9 *pd3dDevice)
{
	HRESULT hr;
	D3DXMATRIX view, proj;
	view = *g_camera.GetViewMatrix();
	proj = *g_camera.GetProjMatrix();

	V(g_effect->SetTechnique(g_hLightShadowTech));
	V(g_effect->SetValue(g_hSpotLight, &g_spotLight, sizeof(SpotLight)));
	V(g_effect->SetValue(g_hEyePosW, g_camera.GetEyePt(), sizeof(D3DXVECTOR3)));
	V(g_effect->SetTexture(g_hShadowMap, g_shadowTexture));
	V(g_effect->Begin(NULL, 0));
	V(g_effect->BeginPass(0));

	//draw scene
	V(g_effect->SetMatrix(g_hWorld, &g_sceneWorld));
	V(g_effect->SetMatrix(g_hWorldInvTran, &g_sceneWorldInvTran));
	V(g_effect->SetMatrix(g_hWVP, &(g_sceneWorld * view * proj)));
	V(g_effect->SetMatrix(g_hLightWVP, &(g_sceneWorldInvTran * g_lightVP)));
	for(DWORD i = 0; i < g_sceneNumMtrl; i++)
	{
		V(g_effect->SetTexture(g_hTex, g_sceneTextures[i]));
		V(g_effect->SetValue(g_hMtrl, &g_sceneMtrls[i], sizeof(Mtrl)));
		V(g_effect->CommitChanges());
		
		V(g_sceneMesh->DrawSubset(i));
	}

	//draw car
	V(g_effect->SetMatrix(g_hWorld, &g_carWorld));
	V(g_effect->SetMatrix(g_hWorldInvTran, &g_carWorldInvTran));
	V(g_effect->SetMatrix(g_hWVP, &(g_carWorld * view * proj)));
	V(g_effect->SetMatrix(g_hLightWVP, &(g_carWorldInvTran * g_lightVP)));
	for(DWORD i = 0; i < g_carNumMtrl; i++)
	{
		V(g_effect->SetTexture(g_hTex, g_whiteTexture));
		V(g_effect->SetValue(g_hMtrl, &g_carMtrls[i], sizeof(Mtrl)));
		V(g_effect->CommitChanges());

		V(g_carMesh->DrawSubset(i));
	}

	V(g_effect->EndPass());
	V(g_effect->End());
}